AJAX and events
Suppose we wanted to allow
each dictionary term name to control the display of the definition that
follows; clicking on the term name would show or hide the associated
definition. With the techniques we have seen so far, this should be
pretty straightforward:
$(document).ready(function() {
$('.term').click(function() {
$(this).siblings('.definition').slideToggle();
});
});
When a term is clicked, this code finds siblings of the element that have a class of definition, and slides them up or down as appropriate.
All seems in order, but a click
does nothing with this code. Unfortunately, the terms have not yet been
added to the document when we attach the click handlers. Even if we managed to attach click handlers to these items, once we clicked on a different letter the handlers would no longer be attached.
This is a common problem with areas of a page populated by AJAX. A popular solution is to rebind
handlers each time the page area is refreshed. This can be cumbersome,
however, as the event binding code needs to be called each time anything
causes the DOM structure of the page to change.
We'll attach the click handler to the document using .live() and catch our clicks that way:
$(document).ready(function() {
$('.term').live('click', function() {
$(this).siblings('.definition').slideToggle();
});
});
The .live() method tells the browser to observe all clicks anywhere on the page. If (and only if) the clicked element matches the .term
selector, then the handler is executed. Now the toggling behavior will
take place on any term, even if it is added by a later AJAX transaction.
Security limitations
For all its utility in crafting dynamic web applications, XMLHttpRequest (the underlying browser technology behind jQuery's AJAX implementation) is subject to strict boundaries. To prevent various cross-site scripting attacks, it is not generally possible to request a document from a server other than the one that hosts the original page.
This is generally a positive situation. For example, some cite the implementation of JSON parsing by using eval() as insecure. If malicious code is present in the data file, it could be run by the eval()
call. However, since the data file must reside on the same server as
the web page itself, the ability to inject code in the data file is
largely equivalent to the ability to inject code in the page directly.
This means that, for the case of loading trusted JSON files, eval() is not a significant security concern.
There are many cases, though,
in which it would be beneficial to load data from a third-party source.
There are several ways to work around the security limitations and allow
this to happen.
One method is to rely on the
server to load the remote data, and then provide it when requested by
the client. This is a very powerful approach as the server can perform
pre-processing on the data as needed. For example, we could load XML
files containing RSS news feeds from several sources, aggregate them
into a single feed on the server, and publish this new file for the
client when it is requested.
To load data from a remote location without
server involvement, we have to get a bit sneakier. A popular approach
for the case of loading foreign JavaScript files is injecting<script> tags on demand. Since jQuery can help us insert new DOM elements, it is simple to do this:
$(document.createElement('script'))
.attr('src', 'http://example.com/example.js')
.appendTo('head');
In fact, the $.getScript()
method will automatically adapt to this technique if it detects a
remote host in its URL argument, so even this is handled for us.
The browser will execute
the loaded script, but there is no mechanism to retrieve results from
the script. For this reason, the technique requires cooperation from the
remote host. The loaded script must take some action, such as setting a
global variable that has an effect on the local environment. Services
that publish scripts that are executable in this way will also provide
an API with which to interact with the remote script.
Another option is to use the<iframe>
HTML tag to load remote data. This element allows any URL to be used as
the source for its data fetching, even if it does not match the host
page's server. The data can be loaded and easily displayed on the
current page. Manipulating the data, however, typically requires the
same cooperation needed for the<script> tag approach; scripts inside the<iframe> need to explicitly provide the data to objects in the parent document.
Using JSONP for remote data
The idea of using<script>
tags to fetch JavaScript files from a remote source can be adapted to
pull in JSON files from another server as well. To do this, we need to
slightly modify the JSON file on the server, however. There are several
mechanisms for doing this, one of which is directly supported by jQuery: JSON with Padding, or JSONP.
The JSONP file format
consists of a standard JSON file that has been wrapped in parentheses
and prepended with an arbitrary text string. This string, the "padding",
is determined by the client requesting the data. Because of the
parentheses, the client can either cause a function to be called or a
variable to be set depending on what is sent as the padding string.
A PHP implementation of the JSONP technique is quite simple:
<?php
print($_GET['callback'] .'('. $data .')');
?>
Here, $data is a variable containing a string representation of a JSON file. When this script is called, the callback query string parameter is prepended to the resulting file that gets returned to the client.
To demonstrate this technique,
we need only slightly modify our earlier JSON example to call this
remote data source instead. The $.getJSON() function makes use of a
special placeholder character, ?, to achieve this.
$(document).ready(function() {
var url = 'http://examples.learningjquery.com/jsonp/g.php';
$('#letter-g a').click(function() {
$.getJSON(url + '?callback=?', function(data) {
$('#dictionary').empty();
$.each(data, function(entryIndex, entry) {
var html = '<div class="entry">';
html += '<h3 class="term">' + entry['term']
+ '</h3>';
html += '<div class="part">' + entry['part']
+ '</div>';
html += '<div class="definition">';
html += entry['definition'];
if (entry['quote']) {
html += '<div class="quote">';
$.each(entry['quote'], function(lineIndex, line) {
html += '<div class="quote-line">' + line
+ '</div>';
});
if (entry['author']) {
html += '<div class="quote-author">'
+ entry['author'] + '</div>';
}
html += '</div>';
}
html += '</div>';
html += '</div>';
$('#dictionary').append(html);
});
});
return false;
});
});
We normally would not be allowed
to fetch JSON from a remote server (examples.learningjquery.com in this
case). However, since this file is set up to provide its data in the
JSONP format, we can obtain the data by appending a query string to our
URL, using ? as a placeholder for the value of the callback argument. When the request is made, jQuery replaces the ? for us, parses the result, and passes it to the success function as data just as if this were a local JSON request.
Note that the same
security cautions hold here as before; whatever the server decides to
return to the browser will execute on the user's computer. The JSONP
technique should only be used with data coming from a trusted source.